/**
 * Vue.js 使用场景大小分析脚本
 *
 * 此脚本用于分析在不同使用场景下 Vue.js 的包大小。
 * 通过模拟各种常见的 API 组合，测试 Tree-shaking 效果和实际打包大小。
 *
 * 主要功能：
 * - 测试多种预设使用场景的打包大小
 * - 比较仅组合式 API 和完整 API 的大小差异
 * - 生成精确的压缩大小报告（minified, gzip, brotli）
 * - 为性能优化和 Bundle 分析提供数据支持
 *
 * @ts-check
 */
import { mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { rollup } from 'rollup'
import nodeResolve from '@rollup/plugin-node-resolve'
import { minify } from '@swc/core'
import replace from '@rollup/plugin-replace'
import { brotliCompressSync, gzipSync } from 'node:zlib'
import { parseArgs } from 'node:util'
import pico from 'picocolors'
import prettyBytes from 'pretty-bytes'

/**
 * 解析命令行参数
 * 支持的参数：
 * - write: 是否将构建产物写入文件系统（用于调试）
 */
const {
  values: { write },
} = parseArgs({
  options: {
    write: {
      type: 'boolean',
      default: false, // 默认不写入文件，仅输出统计信息
    },
  },
})

// 大小报告输出目录
const sizeDir = path.resolve('temp/size')
// Vue.js 运行时版本的入口文件（ESM bundler 格式）
const entry = path.resolve('./packages/vue/dist/vue.runtime.esm-bundler.js')

/**
 * 使用场景预设配置类型定义
 * @typedef {Object} Preset
 * @property {string} name - 预设名称，用于显示和报告
 * @property {string[]} imports - 要导入的 Vue API 列表
 * @property {Record<string, string>} [replace] - 可选的特性开关替换配置
 */

/**
 * 使用场景预设列表
 * 每个预设代表一种常见的 Vue.js 使用模式
 *
 * @type {Preset[]}
 */
const presets = [
  {
    name: 'createApp (CAPI only)', // 仅使用组合式 API 的最小应用
    imports: ['createApp'],
    replace: { __VUE_OPTIONS_API__: 'false' }, // 禁用 Options API 以减小包大小
  },
  {
    name: 'createApp', // 标准的应用创建（包含 Options API）
    imports: ['createApp'],
  },
  {
    name: 'createSSRApp', // 服务端渲染应用
    imports: ['createSSRApp'],
  },
  {
    name: 'defineCustomElement', // 自定义元素（Web Components）
    imports: ['defineCustomElement'],
  },
  {
    name: 'overall', // 综合使用场景，包含多个常用 API
    imports: [
      'createApp', // 应用创建
      'ref', // 响应式引用
      'watch', // 侦听器
      'Transition', // 过渡动画组件
      'KeepAlive', // 缓存组件
      'Suspense', // 异步组件组件
    ],
  },
]

// 启动主函数
main()

/**
 * 主函数：为所有预设生成打包并输出结果
 *
 * 执行流程：
 * 1. 並行执行所有预设的打包任务
 * 2. 收集和显示结果
 * 3. 保存结果到 JSON 文件供后续分析
 */
async function main() {
  console.log()

  /**
   * 为每个预设创建打包任务
   * @type {Promise<{name: string, size: number, gzip: number, brotli: number}>[]}
   */
  const tasks = []
  for (const preset of presets) {
    tasks.push(generateBundle(preset))
  }

  // 等待所有打包任务完成
  const results = await Promise.all(tasks)

  // 输出每个预设的大小统计信息
  for (const r of results) {
    console.log(
      `${pico.green(pico.bold(r.name))} - ` +
        `min:${prettyBytes(r.size, { minimumFractionDigits: 3 })} / ` + // 压缩后大小
        `gzip:${prettyBytes(r.gzip, { minimumFractionDigits: 3 })} / ` + // gzip 压缩后大小
        `brotli:${prettyBytes(r.brotli, { minimumFractionDigits: 3 })}`, // brotli 压缩后大小
    )
  }

  // 确保输出目录存在
  await mkdir(sizeDir, { recursive: true })

  // 保存结果到 JSON 文件，供 size-report.js 使用
  await writeFile(
    path.resolve(sizeDir, '_usages.json'),
    JSON.stringify(Object.fromEntries(results.map(r => [r.name, r])), null, 2),
    'utf-8',
  )
}

/**
 * 为指定预设生成打包并计算大小
 *
 * 这个函数会模拟一个真实的打包场景，使用 Rollup 构建并用 SWC 压缩，
 * 然后计算不同压缩算法下的大小。
 *
 * @param {Preset} preset - 要生成打包的预设配置
 * @returns {Promise<{name: string, size: number, gzip: number, brotli: number}>} 打包结果
 */
async function generateBundle(preset) {
  // 虚拟入口文件 ID
  const id = 'virtual:entry'

  /**
   * 生成虚拟入口文件内容
   * 格式： export { createApp, ref, ... } from 'vue'
   * 这模拟了用户实际使用 Vue.js 的方式
   */
  const content = `export { ${preset.imports.join(', ')} } from '${entry}'`

  /**
   * 使用 Rollup 构建
   * 配置与实际生产环境类似，确保数据的准确性
   */
  const result = await rollup({
    input: id, // 入口文件 ID
    plugins: [
      {
        name: 'usage-size-plugin',
        // 处理虚拟入口文件的解析
        resolveId(_id) {
          if (_id === id) return id
          return null
        },
        // 加载虚拟入口文件的内容
        load(_id) {
          if (_id === id) return content
        },
      },
      // 解析 Node.js 模块依赖
      nodeResolve(),
      // 替换全局常量，模拟生产环境构建
      replace({
        'process.env.NODE_ENV': '"production"', // 生产环境
        __VUE_PROD_DEVTOOLS__: 'false', // 生产环境不启用开发工具
        __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false', // 生产环境不显示水化不匹配详情
        __VUE_OPTIONS_API__: 'true', // 默认启用 Options API
        preventAssignment: true, // 防止意外的赋值替换
        ...preset.replace, // 应用预设特定的替换配置
      }),
    ],
  })

  // 生成代码
  const generated = await result.generate({})
  const bundled = generated.output[0].code

  /**
   * 使用 SWC 进行代码压缩
   * SWC 比 Terser 更快，而且压缩效果也很好
   */
  const minified = (
    await minify(bundled, {
      module: true, // 启用 ES 模块优化
      toplevel: true, // 启用顶层作用域优化
    })
  ).code

  /**
   * 计算不同压缩算法下的文件大小
   */
  const size = minified.length // 压缩后的 JavaScript 代码大小
  const gzip = gzipSync(minified).length // gzip 压缩后大小
  const brotli = brotliCompressSync(minified).length // brotli 压缩后大小

  // 如果指定了 write 参数，将构建产物写入文件系统（用于调试）
  if (write) {
    await writeFile(path.resolve(sizeDir, preset.name + '.js'), bundled)
  }

  /**
   * 返回打包结果
   * 包含预设名称和各种压缩算法下的大小
   */
  return {
    name: preset.name,
    size, // 压缩后大小（字节）
    gzip, // gzip 压缩后大小（字节）
    brotli, // brotli 压缩后大小（字节）
  }
}
